package com.opensource.excel.core;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler;
import cn.hutool.poi.exceptions.POIException;

/**
 * 读取excel模板数据的实现类
 * 
 * @author liyingfu
 *
 */
public class XReader {

	/**
	 * 缓存解析规则的配置信息
	 */
	final static Map<String, XTable> CACHE = new HashMap<>();

	/**
	 * 读取excel， 并把数据赋值转换成对应的实体类 <br/>
	 * 默认第一个sheet页
	 * 
	 * @param <T>
	 * @param filePath 文件路径
	 * @param tableId  配置的ID
	 * @return
	 * @throws Exception
	 */
	public static <T> List<T> getReader(String filePath, String tableId) throws Exception {
		return getReader(FileUtil.file(filePath), tableId);
	}

	/**
	 * 读取excel的数据，并给数据赋值转换成对应的实体类。 <br/>
	 * 
	 * 
	 * @param <T>
	 * @param file    // 读取的文件
	 * @param tableId // 配置的ID
	 * @return
	 * @throws Exception
	 */
	public static <T> List<T> getReader(File file, String tableId) throws Exception {
		List<T> ts = null;
		if (CollUtil.isNotEmpty(CACHE)) {
			ExcelReader reader = ExcelUtil.getReader(file);
			ts = getRow(tableId, reader);
		}
		return ts;
	}

	/**
	 * 读取excel的数据信息
	 * 
	 * @param <T>
	 * @param tableId
	 * @param reader
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	private static <T> List<T> getRow(String tableId, ExcelReader reader) throws InstantiationException, IllegalAccessException, InvocationTargetException {
		List<T>     ts;
		XTable      xTable = CACHE.get(tableId);
		List<XCell> fields = null;
		if (xTable.getHeaderIndex() >= 0) {
			List<Object> header = reader.readRow(xTable.getHeaderIndex());
			fields = parse(header, xTable.getRows().get(xTable.getHeaderIndex()));
			if (CollUtil.isEmpty(fields)) {
				throw new RuntimeException("导入数据和模板必须要有表头信息");
			}
		} else {
			throw new RuntimeException("导入数据和模板必须要有表头信息");
		}
		List<List<Object>> rows = reader.read(xTable.getRowSize());
		ts = new ArrayList<>();
		for (List<Object> row : rows) {
			Object object = newClassAndSetData(xTable, fields, row);
			ts.add((T) object);
		}
		return ts;
	}

	/**
	 * 读取excel的数据，按照具体的第几个sheet读取
	 * 
	 * @param <T>
	 * @param file     文件名
	 * @param tableId  配置的ID
	 * @param sheetNum 第几个sheet，1 表示第一个
	 * @return
	 * @throws Exception
	 */
	public static <T> List<T> getReader(File file, String tableId, int sheetNum) throws Exception {
		List<T> ts = null;
		if (CollUtil.isNotEmpty(CACHE)) {
			ExcelReader reader = ExcelUtil.getReader(file, sheetNum - 1);
			ts = getRow(tableId, reader);
		}
		return ts;
	}

	/**
	 * 通过每行的形式读取数据， 只读取第一个单元格的数据 <br/>
	 * 默认第一个sheet页
	 * 
	 * @param <T>
	 * 
	 * @param file    读取的文件
	 * @param tableId 配置模板的ID
	 * @param handler 每行处理的类
	 */
	public static <T> void getReader(File file, String tableId, XRowHandler<T> handler) {
		getReader(file, tableId, 1, handler);
	}

	/**
	 * 通过每行的形式读取数据 <br/>
	 * 
	 * @param <T>
	 * @param file     读取的文件
	 * @param tableId  配置模板的ID
	 * @param sheetNum sheet页号 从1开始
	 * @param handler  每行处理的类
	 */
	public static <T> void getReader(File file, String tableId, int sheetNum, XRowHandler<T> handler) {
		if (CollUtil.isNotEmpty(CACHE)) {
			XTable xTable = CACHE.get(tableId);
			// 1. 处理解析模板后的结果，先验证表头是否存在
			int headerIndex = xTable.getHeaderIndex();
			if (headerIndex < 0) {
				handler.handleError(new RuntimeException("配置模板必须要有表头信息"));
				return;
			}
			try {
				ExcelUtil.readBySax(file, sheetNum - 1, new RowHandler() {
					private List<XCell> fields = null;

					@SuppressWarnings("unchecked")
					@Override
					public void handle(int sheetIndex, long rowIndex, List<Object> row) {
						// 从excel第几行开始批对表头信息，确认读取的数据规则
						if (rowIndex == headerIndex) {
							List<Object> header = row;
							fields = parse(header, xTable.getRows().get(xTable.getHeaderIndex()));
							if (CollUtil.isEmpty(fields)) {
								throw new POIException("导入数据的表头与模板不匹配");
							}
						} else if (rowIndex > headerIndex) {
							try {
								Object object = newClassAndSetData(xTable, fields, row);
								handler.handle((T) object);
							} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
								throw new POIException(e.getMessage());
							}
						}
					}
				});
			} catch (POIException e) {
				handler.handleError(e);
			}

		}
	}

	/**
	 * 初始化一个类， 并给类赋值
	 * 
	 * @param xTable
	 * @param fields
	 * @param row
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private static Object newClassAndSetData(XTable xTable, List<XCell> fields, List<Object> row)
	        throws InstantiationException, IllegalAccessException, InvocationTargetException {
		if (xTable.getClazz().equals(HashMap.class)) {
			HashMap<String, Object> map = new HashMap<>();
			for (XCell keyCell : fields) {
				Object value = validField(row, keyCell);
				map.put(keyCell.getField(), value);
			}
			return map;
		} else {
			Object object = xTable.getClazz().newInstance();
			for (XCell keyCell : fields) {
				Object value      = validField(row, keyCell);
				String methodName = StrUtil.upperFirstAndAddPre(keyCell.getField(), "set");
				Method method     = ReflectUtil.getMethod(xTable.getClazz(), methodName, keyCell.getType());
				if (null != method && null != value) {
					method.invoke(object, value);
				}
			}
			return object;
		}

	}

	/**
	 * 验证字段值是否为空的问题
	 * 
	 * @param row
	 * @param keyCell
	 * @return
	 * @throws NullPointerException
	 */
	private static Object validField(List<Object> row, XCell keyCell) throws NullPointerException {
		// 如果数据长度小于当前坐标， 直接跳过
		if (row.size() < (keyCell.getCellIndex() + 1)) {
			return null;
		}
		String value = (row.get(keyCell.getCellIndex()) + "").trim();
		if (keyCell.isRequired()) {
			if (StrUtil.isBlankOrUndefined(value)) {
				throw new NullPointerException("字段：【" + keyCell.getText() + "】值不能为空！");
			}
		}
		if (!StrUtil.isBlankOrUndefined(value)) {
			if (keyCell.getType().equals(String.class)) {
				return value;
			}
			if (keyCell.getType().equals(Boolean.class)) {
				return BooleanUtil.toBoolean(value);
			}
			if (keyCell.getType().equals(Long.class)) {
				return NumberUtil.parseLong(value);
			}
			if (keyCell.getType().equals(BigDecimal.class)) {
				return NumberUtil.toBigDecimal(value);
			}
			if (keyCell.getType().equals(Integer.class)) {
				return toInt(value);
			}
			if (keyCell.getType().equals(Date.class)) {
				return DateUtil.parse(value);
			}
			if (keyCell.getType().equals(Double.class)) {
				return NumberUtil.parseNumber(value).doubleValue();
			}
		}
		return null;
	}

	/**
	 * 字符串转int数据， 转不出来时， 返回null
	 * 
	 * @param value
	 * @return
	 */
	private static Integer toInt(String value) {
		try {
			return NumberUtil.parseInt(value);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 对比excel的表头， 对应模板数据解析出模板使用的单元格信息， 按照excel表头顺序重新排列
	 * 
	 * @param header
	 * @param xRow
	 * @return
	 */
	private static List<XCell> parse(List<Object> header, XRow xRow) {
		List<XCell> fields = new ArrayList<>();
		for (int i = 0; i < header.size(); i++) {
			Object cell = header.get(i);
			for (XCell xCell : xRow.getCells()) {
				if (StrUtil.equalsAnyIgnoreCase(StrKit.delSpecialChar(cell + ""), xCell.getText())) {
					xCell.setCellIndex(i);
					fields.add(xCell);

				}
			}
		}
		return fields;
	}

}
